iOS Crash 五

之前介绍符号化文件的时候,使用atos命令可以将符号堆栈信息变的可读。方法如下:

atos -o dYSM -arch **arch -l imageAddress stackAddress

在真实的环境中使用的时候需要准备的dYSM文件远远不止archive出来的app二进制文件。奔溃的堆栈中都会出现系统的库,例如:

CoreFundation
UIkit
libdyld.dylib
...

这些库的符号文件没有在archive的时候一并集成到app.dSYM文件中。在使用atos命令方式解析的时候,你需要找到对应二进制镜像的符号文件才能解析。以当前测试的TestCrash文件为例,需要在Xcode中找到对应的系统符号文件

~/Library/Developer/Xcode/iOS DeviceSupport/

// 当前我测试的系统版本是11.4.1,系统的符号文件在对应版本的文件夹下
~/Library/Developer/Xcode/iOS DeviceSupport/11.4.1 (15G77)/Symbols

常见的Foundation等系统库符号文件在这个文件中

常见的lib符号文件在这个文件中

更多的符号文件可以在这个地址中找到。

符号化文件(swift server后台处理)

符号文件已经准备齐全,之后就是解析的过程了,在之前的文章中,上报的符号文件已经拆分成了diction的方式,每个diction单元中的信息包含:

  • imageName
  • imageLoadAddress
  • stackAddress

解析的时候,先拿imageName去系统库中找有没有匹配的二进制符号文件。这里写了一个简单的代码:

// 这里配置一张表,当前系统可以直接索引路径
func systemSymbolPath(bySystemVersion: String, imageName: String) -> String? {
    let prePath: String = "/Users/handongwang/Library/Developer/Xcode/iOS DeviceSupport"
    let libSuffixPath: String = "Symbols/usr/lib"
    let frameworkSuffixPath: String = "Symbols/System/Library/Frameworks"

    var suffixPath = frameworkSuffixPath
    if imageName.hasPrefix("lib") {
        suffixPath = libSuffixPath
    }

    if bySystemVersion == "11.4.1" {
        return prePath + "/11.4.1 (15G77)/" + suffixPath
    }
    return nil
}

查找的方式通过find command进行,下面是swift是用shell commad的一个简短的封装。(ps:使用perfect自己的SysProcess一直没有成功,使用了如下的方法):

func shell(launchPath: String, arguments: [String]) -> String?
{
    let task = Process()
    task.launchPath = launchPath
    task.arguments = arguments

    let pipe = Pipe()
    task.standardOutput = pipe
    task.launch()

    let data = pipe.fileHandleForReading.readDataToEndOfFile()
    let output = String(data: data, encoding: String.Encoding.utf8)
    if let value = output, value.count > 0 {
        //remove newline character.
        let lastIndex = value.index(before: value.endIndex)
        return String(value[value.startIndex ..< lastIndex])
    }
    return output
}

在实际收到crash上传文件的出路handler如下:

public static func requestHandler(request: HTTPRequest, response: HTTPResponse) {
        print(request.postFileUploads ?? "nothing")

        func errorHandler(response: HTTPResponse) {
            response.sendError(message: "数据解析错误")
        }

        if let uploads = request.postFileUploads, uploads.count > 0 {
            // 当前测试中智慧传递一个文件
            if let upload = uploads.first {
                if let data = NSData.init(contentsOfFile: upload.tmpFileName) {
                    if let dict = try? JSONSerialization.jsonObject(with: data as Data, options: .mutableContainers), let dictObject = dict as? NSDictionary {
                        // 数据解析成功,使用命令行解析数据
                        if let stackInfoArray = dictObject["stackInfo"] as? NSArray,
                            stackInfoArray.count > 0,
                            let arch = dictObject["arch"] as? String,
                            let systemVersion = dictObject["systemVersion"] as? String {
                            let mutableArray = NSMutableArray()
                            for info in stackInfoArray {
                                if let infoDict = info as? [String : String], let nameString = infoDict["name"],
                                    let imageAddress = infoDict["imageAddress"], let strStackAddress = infoDict["strStackAddress"]{
                                    if let systemSymbolPath = systemSymbolPath(bySystemVersion: systemVersion),
                                        systemSymbolPath.count > 0 {
                                        // 系统文件中找符号文件
                                        if let findPath = shell(launchPath: "/usr/bin/find", arguments: ["\(systemSymbolPath)" , "-iname" , "\(nameString)"]), findPath.count > 0 {
                                            // 这里需要去除换行符
                                            let pathWhoutNewLine = findPath.stringByReplacing(string: "\n", withString: "")
                                            if let output = shell(launchPath: "/usr/bin/atos", arguments: ["-arch" , "\(arch)" , "-o" , "\(pathWhoutNewLine)" , "-l" , "\(imageAddress)" , "\(strStackAddress)"]) {
                                                mutableArray.add(["name": nameString, "symbolString": output])
                                            }
                                        }
                                        else {
                                            if let output = shell(launchPath: "/usr/bin/atos", arguments: ["-arch" , "\(arch)" , "-o" , "/Users/handongwang/Desktop/TestCrashFile/TestCrash.app.dSYM/Contents/Resources/DWARF/TestCrash" , "-l" , "\(imageAddress)" , "\(strStackAddress)"]) {
                                                mutableArray.add(["name": nameString, "symbolString": output])
                                            }
                                        }
                                    }
                                }
                            }
                            print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
                            print(mutableArray)
                        }
                    }
                    else {
                        errorHandler(response: response)
                    }
                }
                else {
                    errorHandler(response: response)
                }
            }
            else {
                errorHandler(response: response)
            }
        }
        else {
            errorHandler(response: response)
        }
    }

解析的过程变成了找到对应的符号文件,使用atos命令逐个diction开始解析

// 系统libdyld.dylib文件符号化过程
atos -arch arm64 -o ~/Library/Developer/Xcode/iOS\ DeviceSupport/11.4.1\ \(15G77\)/Symbols/usr/lib/system/libdyld.dylib -l 0x1829E1000 0x00000001829e1fc0
// 结果展示
start (in libdyld.dylib) + 4

// 系统UIKit文件符号化过程
atos -arch arm64 -o ~/Library/Developer/Xcode/iOS\ DeviceSupport/11.4.1\ \(15G77\)/Symbols/System/Library/Frameworks/UIKit.framework/UIKit -l 0x18CC53000 0x000000018ce71890
// 结果展示
-[UIWindow sendEvent:] (in UIKit) + 3160

最终手动符号文件完成打印的结果为:

(
        {
        name = CoreFoundation;
        symbolString = "__exceptionPreprocess (in CoreFoundation) + 252";
    },
        {
        name = "libobjc.A.dylib";
        symbolString = "objc_exception_throw (in libobjc.A.dylib) + 56";
    },
        {
        name = CoreFoundation;
        symbolString = "_CFArgv (in CoreFoundation) + 0";
    },
        {
        name = CoreFoundation;
        symbolString = "-[__NSArrayM insertObject:atIndex:] (in CoreFoundation) + 1412";
    },
        {
        name = TestCrash;
        symbolString = "-[ViewController function9] (in TestCrash) (ViewController.m:89)";
    },
        {
        name = UIKit;
        symbolString = "-[UIApplication sendAction:to:from:forEvent:] (in UIKit) + 96";
    },
        {
        name = UIKit;
        symbolString = "-[UIControl sendAction:to:forEvent:] (in UIKit) + 80";
    },
        {
        name = UIKit;
        symbolString = "-[UIControl _sendActionsForEvents:withEvent:] (in UIKit) + 440";
    },
        {
        name = UIKit;
        symbolString = "-[UIControl touchesEnded:withEvent:] (in UIKit) + 572";
    },
        {
        name = UIKit;
        symbolString = "-[UIWindow _sendTouchesForEvent:] (in UIKit) + 2428";
    },
        {
        name = UIKit;
        symbolString = "-[UIWindow sendEvent:] (in UIKit) + 3160";
    },
        {
        name = UIKit;
        symbolString = "-[UIApplication sendEvent:] (in UIKit) + 340";
    },
        {
        name = UIKit;
        symbolString = "__dispatchPreprocessedEventFromEventQueue (in UIKit) + 2340";
    },
        {
        name = UIKit;
        symbolString = "__handleEventQueueInternal (in UIKit) + 4744";
    },
        {
        name = UIKit;
        symbolString = "__handleHIDEventFetcherDrain (in UIKit) + 152";
    },
        {
        name = CoreFoundation;
        symbolString = "__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ (in CoreFoundation) + 24";
    },
        {
        name = CoreFoundation;
        symbolString = "__CFRunLoopDoSources0 (in CoreFoundation) + 276";
    },
        {
        name = CoreFoundation;
        symbolString = "__CFRunLoopRun (in CoreFoundation) + 1204";
    },
        {
        name = CoreFoundation;
        symbolString = "CFRunLoopRunSpecific (in CoreFoundation) + 552";
    },
        {
        name = GraphicsServices;
        symbolString = "getProgressBinaryImagesInfo (in TestCrash) (LHCrashTool.m:244)";
    },
        {
        name = UIKit;
        symbolString = "UIApplicationMain (in UIKit) + 236";
    },
        {
        name = TestCrash;
        symbolString = "main (in TestCrash) (main.m:14)";
    },
        {
        name = "libdyld.dylib";
        symbolString = "start (in libdyld.dylib) + 4";
    }
)

在看一眼xcode中自动符号的文件:

和上面手动符号化的文件一样。到此符号化文件已经完成。当前还存在一些问题:

  • 符号文件的时间过程,需要调研,单个文件的符号时间在3s左右,时间耗时过长,需要优化

存储符号化文件

存储符号化的过程,当前使用的是swift server + mongodb的方式。

参考

iOS 系统符号文件地址